package com.nageoffer.onecoupon.merchant.admin.dao.sharding;

import lombok.Getter;
import org.apache.shardingsphere.infra.util.exception.ShardingSpherePreconditions;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import org.apache.shardingsphere.sharding.exception.algorithm.sharding.ShardingAlgorithmInitializationException;

import java.util.Collection;
import java.util.List;
import java.util.Properties;

/**
 * 自定义分库算法：基于哈希取模的分库策略
 * 作者 伟大哥
 * 日期 2025/3/29,14:19
 **/

public class DBHashModShardingAlgorithm implements StandardShardingAlgorithm<Long> {
    @Getter
    private Properties props;

    private int shardingCount;
    private static final String SHARDING_COUNT_KEY = "sharding-count";


    /**
     * 精确分片方法，用于处理精确查询的分片逻辑。该方法根据分片键的精确值计算出目标数据源，并返回该数据源的名称。
     *
     * @param availableTargetNames 所有可用的数据源名称集合
     * @param shardingValue 包含分片键的精确值及其相关信息
     * @return 目标数据源名称
     * @throws IllegalArgumentException 如果未找到匹配的目标数据源，则抛出此异常
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
        // 获取分片键的精确值
        long id = shardingValue.getValue();

        // 计算可用数据源的数量
        int dbSize = availableTargetNames.size();

        // 根据分片键的哈希值计算目标数据源的索引
        int mod = (int) hashShardingValue(id) % shardingCount / (shardingCount / dbSize);

        // 遍历可用数据源集合，找到与计算出的索引匹配的数据源
        int index = 0;
        for (String targetName : availableTargetNames) {
            if (index == mod) {
                return targetName;
            }
            index++;
        }

        // 如果未找到匹配的数据源，抛出异常
        throw new IllegalArgumentException("No target found for value: " + id);
    }


    /**
     * 范围分片方法，用于处理范围查询的分片逻辑
     *
     * @param availableTargetNames 所有可用的数据源名称集合，表示当前可用的数据源列表
     * @param shardingValue 分片键的范围值，包含分片键的范围信息，用于确定数据分片的范围
     * @return 目标数据源名称集合，返回符合分片条件的数据源名称列表
     */
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
        // 当前无范围分片场景，默认返回空集合
        return List.of();
    }


    @Override
    public void init(Properties props) {
        this.props = props;
        shardingCount = getShardingCount(props);
    }

    private int getShardingCount(final Properties props) {
        ShardingSpherePreconditions.checkState(props.containsKey(SHARDING_COUNT_KEY), () -> new ShardingAlgorithmInitializationException(getType(), "Sharding count cannot be null."));
        return Integer.parseInt(props.getProperty(SHARDING_COUNT_KEY));
    }

    /**
     * 计算分片值的哈希值，并返回其绝对值。
     *
     * 该函数用于将分片值转换为一个非负的长整型哈希值，通常用于分片算法中确定数据的分片位置。
     *
     * @param shardingValue 分片值，必须实现 {@link Comparable} 接口，以便能够计算其哈希值。
     * @return 返回分片值的哈希值的绝对值，确保结果为非负的长整型数值。
     */
    private long hashShardingValue(final Comparable<?> shardingValue) {
        return Math.abs((long) shardingValue.hashCode());
    }

}
